// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <map>
#include <vector>

// Include views_test_base.h first because the definition of None in X.h
// conflicts with the definition of None in gtest-type-util.h
#include "ui/views/test/views_test_base.h"

#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/views/widget/desktop_aura/desktop_cursor_loader_updater.h"
#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/desktop_aura/x11_move_loop.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/drag_drop_delegate.h"

#include <X11/Xlib.h>

namespace views {

namespace {

    const char* kAtomsToCache[] = {
        "XdndActionCopy",
        "XdndDrop",
        "XdndEnter",
        "XdndFinished",
        "XdndLeave",
        "XdndPosition",
        "XdndStatus",
        "XdndTypeList",
        NULL
    };

    class TestDragDropClient;

    // Collects messages which would otherwise be sent to |xid_| via
    // SendXClientEvent().
    class ClientMessageEventCollector {
    public:
        ClientMessageEventCollector(::Window xid, TestDragDropClient* client);
        virtual ~ClientMessageEventCollector();

        // Returns true if |events_| is non-empty.
        bool HasEvents() const
        {
            return !events_.empty();
        }

        // Pops all of |events_| and returns the popped events in the order that they
        // were on the stack
        std::vector<XClientMessageEvent> PopAllEvents();

        // Adds |event| to the stack.
        void RecordEvent(const XClientMessageEvent& event);

    private:
        ::Window xid_;

        // Not owned.
        TestDragDropClient* client_;

        std::vector<XClientMessageEvent> events_;

        DISALLOW_COPY_AND_ASSIGN(ClientMessageEventCollector);
    };

    // An implementation of X11MoveLoop where RunMoveLoop() always starts the move
    // loop.
    class TestMoveLoop : public X11MoveLoop {
    public:
        explicit TestMoveLoop(X11MoveLoopDelegate* delegate);
        ~TestMoveLoop() override;

        // Returns true if the move loop is running.
        bool IsRunning() const;

        // X11MoveLoop:
        bool RunMoveLoop(aura::Window* window, gfx::NativeCursor cursor) override;
        void UpdateCursor(gfx::NativeCursor cursor) override;
        void EndMoveLoop() override;

    private:
        // Not owned.
        X11MoveLoopDelegate* delegate_;

        // Ends the move loop.
        base::Closure quit_closure_;

        bool is_running_;
    };

    // Implementation of DesktopDragDropClientAuraX11 which short circuits
    // FindWindowFor().
    class SimpleTestDragDropClient : public DesktopDragDropClientAuraX11 {
    public:
        SimpleTestDragDropClient(aura::Window*,
            DesktopNativeCursorManager* cursor_manager);
        ~SimpleTestDragDropClient() override;

        // Sets |xid| as the topmost window for all mouse positions.
        void SetTopmostXWindow(XID xid);

        // Returns true if the move loop is running.
        bool IsMoveLoopRunning();

    private:
        // DesktopDragDropClientAuraX11:
        scoped_ptr<X11MoveLoop> CreateMoveLoop(
            X11MoveLoopDelegate* delegate) override;
        XID FindWindowFor(const gfx::Point& screen_point) override;

        // The XID of the window which is simulated to be the topmost window.
        XID target_xid_;

        // The move loop. Not owned.
        TestMoveLoop* loop_;

        DISALLOW_COPY_AND_ASSIGN(SimpleTestDragDropClient);
    };

    // Implementation of DesktopDragDropClientAuraX11 which works with a fake
    // |DesktopDragDropClientAuraX11::source_current_window_|.
    class TestDragDropClient : public SimpleTestDragDropClient {
    public:
        // The location in screen coordinates used for the synthetic mouse moves
        // generated in SetTopmostXWindowAndMoveMouse().
        static const int kMouseMoveX;
        static const int kMouseMoveY;

        TestDragDropClient(aura::Window* window,
            DesktopNativeCursorManager* cursor_manager);
        ~TestDragDropClient() override;

        // Returns the XID of the window which initiated the drag.
        ::Window source_xwindow()
        {
            return source_xid_;
        }

        // Returns the Atom with |name|.
        Atom GetAtom(const char* name);

        // Returns true if the event's message has |type|.
        bool MessageHasType(const XClientMessageEvent& event,
            const char* type);

        // Sets |collector| to collect XClientMessageEvents which would otherwise
        // have been sent to the drop target window.
        void SetEventCollectorFor(::Window xid,
            ClientMessageEventCollector* collector);

        // Builds an XdndStatus message and sends it to
        // DesktopDragDropClientAuraX11.
        void OnStatus(XID target_window,
            bool will_accept_drop,
            ::Atom accepted_action);

        // Builds an XdndFinished message and sends it to
        // DesktopDragDropClientAuraX11.
        void OnFinished(XID target_window,
            bool accepted_drop,
            ::Atom performed_action);

        // Sets |xid| as the topmost window at the current mouse position and
        // generates a synthetic mouse move.
        void SetTopmostXWindowAndMoveMouse(::Window xid);

    private:
        // DesktopDragDropClientAuraX11:
        void SendXClientEvent(::Window xid, XEvent* event) override;

        // The XID of the window which initiated the drag.
        ::Window source_xid_;

        // Map of ::Windows to the collector which intercepts XClientMessageEvents
        // for that window.
        std::map<::Window, ClientMessageEventCollector*> collectors_;

        ui::X11AtomCache atom_cache_;

        DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
    };

    ///////////////////////////////////////////////////////////////////////////////
    // ClientMessageEventCollector

    ClientMessageEventCollector::ClientMessageEventCollector(
        ::Window xid,
        TestDragDropClient* client)
        : xid_(xid)
        , client_(client)
    {
        client->SetEventCollectorFor(xid, this);
    }

    ClientMessageEventCollector::~ClientMessageEventCollector()
    {
        client_->SetEventCollectorFor(xid_, NULL);
    }

    std::vector<XClientMessageEvent> ClientMessageEventCollector::PopAllEvents()
    {
        std::vector<XClientMessageEvent> to_return;
        to_return.swap(events_);
        return to_return;
    }

    void ClientMessageEventCollector::RecordEvent(
        const XClientMessageEvent& event)
    {
        events_.push_back(event);
    }

    ///////////////////////////////////////////////////////////////////////////////
    // TestMoveLoop

    TestMoveLoop::TestMoveLoop(X11MoveLoopDelegate* delegate)
        : delegate_(delegate)
        , is_running_(false)
    {
    }

    TestMoveLoop::~TestMoveLoop()
    {
    }

    bool TestMoveLoop::IsRunning() const
    {
        return is_running_;
    }

    bool TestMoveLoop::RunMoveLoop(
        aura::Window* window,
        gfx::NativeCursor cursor)
    {
        is_running_ = true;
        base::RunLoop run_loop;
        quit_closure_ = run_loop.QuitClosure();
        run_loop.Run();
        return true;
    }

    void TestMoveLoop::UpdateCursor(gfx::NativeCursor cursor)
    {
    }

    void TestMoveLoop::EndMoveLoop()
    {
        if (is_running_) {
            delegate_->OnMoveLoopEnded();
            is_running_ = false;
            quit_closure_.Run();
        }
    }

    ///////////////////////////////////////////////////////////////////////////////
    // SimpleTestDragDropClient

    SimpleTestDragDropClient::SimpleTestDragDropClient(
        aura::Window* window,
        DesktopNativeCursorManager* cursor_manager)
        : DesktopDragDropClientAuraX11(window,
            cursor_manager,
            gfx::GetXDisplay(),
            window->GetHost()->GetAcceleratedWidget())
        , target_xid_(None)
        , loop_(NULL)
    {
    }

    SimpleTestDragDropClient::~SimpleTestDragDropClient()
    {
    }

    void SimpleTestDragDropClient::SetTopmostXWindow(XID xid)
    {
        target_xid_ = xid;
    }

    bool SimpleTestDragDropClient::IsMoveLoopRunning()
    {
        return loop_->IsRunning();
    }

    scoped_ptr<X11MoveLoop> SimpleTestDragDropClient::CreateMoveLoop(
        X11MoveLoopDelegate* delegate)
    {
        loop_ = new TestMoveLoop(delegate);
        return make_scoped_ptr(loop_);
    }

    XID SimpleTestDragDropClient::FindWindowFor(const gfx::Point& screen_point)
    {
        return target_xid_;
    }

    ///////////////////////////////////////////////////////////////////////////////
    // TestDragDropClient

    // static
    const int TestDragDropClient::kMouseMoveX = 100;

    // static
    const int TestDragDropClient::kMouseMoveY = 200;

    TestDragDropClient::TestDragDropClient(
        aura::Window* window,
        DesktopNativeCursorManager* cursor_manager)
        : SimpleTestDragDropClient(window, cursor_manager)
        , source_xid_(window->GetHost()->GetAcceleratedWidget())
        , atom_cache_(gfx::GetXDisplay(), kAtomsToCache)
    {
    }

    TestDragDropClient::~TestDragDropClient()
    {
    }

    Atom TestDragDropClient::GetAtom(const char* name)
    {
        return atom_cache_.GetAtom(name);
    }

    bool TestDragDropClient::MessageHasType(const XClientMessageEvent& event,
        const char* type)
    {
        return event.message_type == atom_cache_.GetAtom(type);
    }

    void TestDragDropClient::SetEventCollectorFor(
        ::Window xid,
        ClientMessageEventCollector* collector)
    {
        if (collector)
            collectors_[xid] = collector;
        else
            collectors_.erase(xid);
    }

    void TestDragDropClient::OnStatus(XID target_window,
        bool will_accept_drop,
        ::Atom accepted_action)
    {
        XClientMessageEvent event;
        event.message_type = atom_cache_.GetAtom("XdndStatus");
        event.format = 32;
        event.window = source_xid_;
        event.data.l[0] = target_window;
        event.data.l[1] = will_accept_drop ? 1 : 0;
        event.data.l[2] = 0;
        event.data.l[3] = 0;
        event.data.l[4] = accepted_action;
        OnXdndStatus(event);
    }

    void TestDragDropClient::OnFinished(XID target_window,
        bool accepted_drop,
        ::Atom performed_action)
    {
        XClientMessageEvent event;
        event.message_type = atom_cache_.GetAtom("XdndFinished");
        event.format = 32;
        event.window = source_xid_;
        event.data.l[0] = target_window;
        event.data.l[1] = accepted_drop ? 1 : 0;
        event.data.l[2] = performed_action;
        event.data.l[3] = 0;
        event.data.l[4] = 0;
        OnXdndFinished(event);
    }

    void TestDragDropClient::SetTopmostXWindowAndMoveMouse(::Window xid)
    {
        SetTopmostXWindow(xid);
        OnMouseMovement(gfx::Point(kMouseMoveX, kMouseMoveY), ui::EF_NONE,
            ui::EventTimeForNow());
    }

    void TestDragDropClient::SendXClientEvent(::Window xid, XEvent* event)
    {
        std::map<::Window, ClientMessageEventCollector*>::iterator it = collectors_.find(xid);
        if (it != collectors_.end())
            it->second->RecordEvent(event->xclient);
    }

} // namespace

class DesktopDragDropClientAuraX11Test : public ViewsTestBase {
public:
    DesktopDragDropClientAuraX11Test()
    {
    }

    ~DesktopDragDropClientAuraX11Test() override { }

    int StartDragAndDrop()
    {
        ui::OSExchangeData data;
        data.SetString(base::ASCIIToUTF16("Test"));

        return client_->StartDragAndDrop(
            data,
            widget_->GetNativeWindow()->GetRootWindow(),
            widget_->GetNativeWindow(),
            gfx::Point(),
            ui::DragDropTypes::DRAG_COPY,
            ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
    }

    // ViewsTestBase:
    void SetUp() override
    {
        ViewsTestBase::SetUp();

        // Create widget to initiate the drags.
        widget_.reset(new Widget);
        Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
        params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
        params.native_widget = new DesktopNativeWidgetAura(widget_.get());
        params.bounds = gfx::Rect(100, 100);
        widget_->Init(params);
        widget_->Show();

        cursor_manager_.reset(new DesktopNativeCursorManager(
            DesktopCursorLoaderUpdater::Create()));

        client_.reset(new TestDragDropClient(widget_->GetNativeWindow(),
            cursor_manager_.get()));
        client_->Init();
    }

    void TearDown() override
    {
        client_.reset();
        cursor_manager_.reset();
        widget_.reset();
        ViewsTestBase::TearDown();
    }

    TestDragDropClient* client()
    {
        return client_.get();
    }

private:
    scoped_ptr<TestDragDropClient> client_;
    scoped_ptr<DesktopNativeCursorManager> cursor_manager_;

    // The widget used to initiate drags.
    scoped_ptr<Widget> widget_;

    DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientAuraX11Test);
};

namespace {

    void BasicStep2(TestDragDropClient* client, XID toplevel)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        ClientMessageEventCollector collector(toplevel, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel);

        // XdndEnter should have been sent to |toplevel| before the XdndPosition
        // message.
        std::vector<XClientMessageEvent> events = collector.PopAllEvents();
        ASSERT_EQ(2u, events.size());

        EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
        EXPECT_EQ(client->source_xwindow(),
            static_cast<XID>(events[0].data.l[0]));
        EXPECT_EQ(1, events[0].data.l[1] & 1);
        std::vector<Atom> targets;
        ui::GetAtomArrayProperty(client->source_xwindow(), "XdndTypeList", &targets);
        EXPECT_FALSE(targets.empty());

        EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
        EXPECT_EQ(client->source_xwindow(),
            static_cast<XID>(events[0].data.l[0]));
        const long kCoords = TestDragDropClient::kMouseMoveX << 16 | TestDragDropClient::kMouseMoveY;
        EXPECT_EQ(kCoords, events[1].data.l[2]);
        EXPECT_EQ(client->GetAtom("XdndActionCopy"),
            static_cast<Atom>(events[1].data.l[4]));

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));

        // Because there is no unprocessed XdndPosition, the drag drop client should
        // send XdndDrop immediately after the mouse is released.
        client->OnMouseReleased();

        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
        EXPECT_EQ(client->source_xwindow(),
            static_cast<XID>(events[0].data.l[0]));

        // Send XdndFinished to indicate that the drag drop client can cleanup any
        // data related to this drag. The move loop should end only after the
        // XdndFinished message was received.
        EXPECT_TRUE(client->IsMoveLoopRunning());
        client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

    void BasicStep3(TestDragDropClient* client, XID toplevel)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        ClientMessageEventCollector collector(toplevel, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel);

        std::vector<XClientMessageEvent> events = collector.PopAllEvents();
        ASSERT_EQ(2u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
        client->SetTopmostXWindowAndMoveMouse(toplevel);
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));

        // We have not received an XdndStatus ack for the second XdndPosition message.
        // Test that sending XdndDrop is delayed till the XdndStatus ack is received.
        client->OnMouseReleased();
        EXPECT_FALSE(collector.HasEvents());

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));

        EXPECT_TRUE(client->IsMoveLoopRunning());
        client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

} // namespace

TEST_F(DesktopDragDropClientAuraX11Test, Basic)
{
    XID toplevel = 1;

    base::MessageLoop::current()->PostTask(FROM_HERE,
        base::Bind(&BasicStep2,
            client(),
            toplevel));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);

    // Do another drag and drop to test that the data is properly cleaned up as a
    // result of the XdndFinished message.
    base::MessageLoop::current()->PostTask(FROM_HERE,
        base::Bind(&BasicStep3,
            client(),
            toplevel));
    result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}

namespace {

    void TargetDoesNotRespondStep2(TestDragDropClient* client)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        XID toplevel = 1;
        ClientMessageEventCollector collector(toplevel, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel);

        std::vector<XClientMessageEvent> events = collector.PopAllEvents();
        ASSERT_EQ(2u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));

        client->OnMouseReleased();
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

} // namespace

// Test that we do not wait for the target to send XdndStatus if we have not
// received any XdndStatus messages at all from the target. The Unity
// DNDCollectionWindow is an example of an XdndAware target which does not
// respond to XdndPosition messages at all.
TEST_F(DesktopDragDropClientAuraX11Test, TargetDoesNotRespond)
{
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&TargetDoesNotRespondStep2, client()));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}

namespace {

    void QueuePositionStep2(TestDragDropClient* client)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        XID toplevel = 1;
        ClientMessageEventCollector collector(toplevel, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel);
        client->SetTopmostXWindowAndMoveMouse(toplevel);
        client->SetTopmostXWindowAndMoveMouse(toplevel);

        std::vector<XClientMessageEvent> events = collector.PopAllEvents();
        ASSERT_EQ(2u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(collector.HasEvents());

        client->OnMouseReleased();
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));

        EXPECT_TRUE(client->IsMoveLoopRunning());
        client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

} // namespace

// Test that XdndPosition messages are queued till the pending XdndPosition
// message is acked via an XdndStatus message.
TEST_F(DesktopDragDropClientAuraX11Test, QueuePosition)
{
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&QueuePositionStep2, client()));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}

namespace {

    void TargetChangesStep2(TestDragDropClient* client)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        XID toplevel1 = 1;
        ClientMessageEventCollector collector1(toplevel1, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel1);

        std::vector<XClientMessageEvent> events1 = collector1.PopAllEvents();
        ASSERT_EQ(2u, events1.size());
        EXPECT_TRUE(client->MessageHasType(events1[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events1[1], "XdndPosition"));

        XID toplevel2 = 2;
        ClientMessageEventCollector collector2(toplevel2, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel2);

        // It is possible for |toplevel1| to send XdndStatus after the source has sent
        // XdndLeave but before |toplevel1| has received the XdndLeave message. The
        // XdndStatus message should be ignored.
        client->OnStatus(toplevel1, true, client->GetAtom("XdndActionCopy"));
        events1 = collector1.PopAllEvents();
        ASSERT_EQ(1u, events1.size());
        EXPECT_TRUE(client->MessageHasType(events1[0], "XdndLeave"));

        std::vector<XClientMessageEvent> events2 = collector2.PopAllEvents();
        ASSERT_EQ(2u, events2.size());
        EXPECT_TRUE(client->MessageHasType(events2[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events2[1], "XdndPosition"));

        client->OnStatus(toplevel2, true, client->GetAtom("XdndActionCopy"));
        client->OnMouseReleased();
        events2 = collector2.PopAllEvents();
        ASSERT_EQ(1u, events2.size());
        EXPECT_TRUE(client->MessageHasType(events2[0], "XdndDrop"));

        EXPECT_TRUE(client->IsMoveLoopRunning());
        client->OnFinished(toplevel2, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

} // namespace

// Test the behavior when the target changes during a drag.
TEST_F(DesktopDragDropClientAuraX11Test, TargetChanges)
{
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&TargetChangesStep2, client()));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}

namespace {

    void RejectAfterMouseReleaseStep2(TestDragDropClient* client)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        XID toplevel = 1;
        ClientMessageEventCollector collector(toplevel, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel);

        std::vector<XClientMessageEvent> events = collector.PopAllEvents();
        ASSERT_EQ(2u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(collector.HasEvents());

        // Send another mouse move such that there is a pending XdndPosition.
        client->SetTopmostXWindowAndMoveMouse(toplevel);
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));

        client->OnMouseReleased();
        // Reject the drop.
        client->OnStatus(toplevel, false, None);

        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

    void RejectAfterMouseReleaseStep3(TestDragDropClient* client)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        XID toplevel = 2;
        ClientMessageEventCollector collector(toplevel, client);
        client->SetTopmostXWindowAndMoveMouse(toplevel);

        std::vector<XClientMessageEvent> events = collector.PopAllEvents();
        ASSERT_EQ(2u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
        EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));

        client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
        EXPECT_FALSE(collector.HasEvents());

        client->OnMouseReleased();
        events = collector.PopAllEvents();
        ASSERT_EQ(1u, events.size());
        EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));

        EXPECT_TRUE(client->IsMoveLoopRunning());
        client->OnFinished(toplevel, false, None);
        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

} // namespace

// Test that the source sends XdndLeave instead of XdndDrop if the drag
// operation is rejected after the mouse is released.
TEST_F(DesktopDragDropClientAuraX11Test, RejectAfterMouseRelease)
{
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&RejectAfterMouseReleaseStep2, client()));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);

    // Repeat the test but reject the drop in the XdndFinished message instead.
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&RejectAfterMouseReleaseStep3, client()));
    result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}

namespace {

    // DragDropDelegate which counts the number of each type of drag-drop event and
    // keeps track of the most recent drag-drop event.
    class TestDragDropDelegate : public aura::client::DragDropDelegate {
    public:
        TestDragDropDelegate()
            : num_enters_(0)
            , num_updates_(0)
            , num_exits_(0)
            , num_drops_(0)
            , last_event_flags_(0)
        {
        }
        ~TestDragDropDelegate() override { }

        int num_enters() const { return num_enters_; }
        int num_updates() const { return num_updates_; }
        int num_exits() const { return num_exits_; }
        int num_drops() const { return num_drops_; }
        gfx::Point last_event_mouse_position() const
        {
            return last_event_mouse_position_;
        }
        int last_event_flags() const { return last_event_flags_; }

    private:
        // aura::client::DragDropDelegate:
        void OnDragEntered(const ui::DropTargetEvent& event) override
        {
            ++num_enters_;
            last_event_mouse_position_ = event.location();
            last_event_flags_ = event.flags();
        }

        int OnDragUpdated(const ui::DropTargetEvent& event) override
        {
            ++num_updates_;
            last_event_mouse_position_ = event.location();
            last_event_flags_ = event.flags();
            return ui::DragDropTypes::DRAG_COPY;
        }

        void OnDragExited() override
        {
            ++num_exits_;
        }

        int OnPerformDrop(const ui::DropTargetEvent& event) override
        {
            ++num_drops_;
            last_event_mouse_position_ = event.location();
            last_event_flags_ = event.flags();
            return ui::DragDropTypes::DRAG_COPY;
        }

        int num_enters_;
        int num_updates_;
        int num_exits_;
        int num_drops_;

        gfx::Point last_event_mouse_position_;
        int last_event_flags_;

        DISALLOW_COPY_AND_ASSIGN(TestDragDropDelegate);
    };

} // namespace

// Test harness for tests where the drag and drop source and target are both
// Chrome windows.
class DesktopDragDropClientAuraX11ChromeSourceTargetTest
    : public ViewsTestBase {
public:
    DesktopDragDropClientAuraX11ChromeSourceTargetTest()
    {
    }

    ~DesktopDragDropClientAuraX11ChromeSourceTargetTest() override { }

    int StartDragAndDrop()
    {
        ui::OSExchangeData data;
        data.SetString(base::ASCIIToUTF16("Test"));

        return client_->StartDragAndDrop(
            data,
            widget_->GetNativeWindow()->GetRootWindow(),
            widget_->GetNativeWindow(),
            gfx::Point(),
            ui::DragDropTypes::DRAG_COPY,
            ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
    }

    // ViewsTestBase:
    void SetUp() override
    {
        ViewsTestBase::SetUp();

        // Create widget to initiate the drags.
        widget_.reset(new Widget);
        Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
        params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
        params.native_widget = new DesktopNativeWidgetAura(widget_.get());
        params.bounds = gfx::Rect(100, 100);
        widget_->Init(params);
        widget_->Show();

        cursor_manager_.reset(new DesktopNativeCursorManager(
            DesktopCursorLoaderUpdater::Create()));

        client_.reset(new SimpleTestDragDropClient(widget_->GetNativeWindow(),
            cursor_manager_.get()));
        client_->Init();
    }

    void TearDown() override
    {
        client_.reset();
        cursor_manager_.reset();
        widget_.reset();
        ViewsTestBase::TearDown();
    }

    SimpleTestDragDropClient* client()
    {
        return client_.get();
    }

private:
    scoped_ptr<SimpleTestDragDropClient> client_;
    scoped_ptr<DesktopNativeCursorManager> cursor_manager_;

    // The widget used to initiate drags.
    scoped_ptr<Widget> widget_;

    DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientAuraX11ChromeSourceTargetTest);
};

namespace {

    void ChromeSourceTargetStep2(SimpleTestDragDropClient* client,
        int modifier_flags)
    {
        EXPECT_TRUE(client->IsMoveLoopRunning());

        scoped_ptr<Widget> target_widget(new Widget);
        Widget::InitParams target_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
        target_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
        target_params.native_widget = new DesktopNativeWidgetAura(target_widget.get());
        target_params.bounds = gfx::Rect(100, 100);
        target_widget->Init(target_params);
        target_widget->Show();

        TestDragDropDelegate* delegate = new TestDragDropDelegate;
        aura::client::SetDragDropDelegate(target_widget->GetNativeWindow(), delegate);

        client->SetTopmostXWindow(
            target_widget->GetNativeView()->GetHost()->GetAcceleratedWidget());

        gfx::Rect target_widget_bounds_in_screen = target_widget->GetWindowBoundsInScreen();
        gfx::Point point1_in_screen = target_widget_bounds_in_screen.CenterPoint();
        gfx::Point point1_in_target_widget(
            target_widget_bounds_in_screen.width() / 2,
            target_widget_bounds_in_screen.height() / 2);
        gfx::Point point2_in_screen = point1_in_screen + gfx::Vector2d(1, 0);
        gfx::Point point2_in_target_widget = point1_in_target_widget + gfx::Vector2d(1, 0);

        client->OnMouseMovement(point1_in_screen, modifier_flags,
            ui::EventTimeForNow());
        EXPECT_EQ(1, delegate->num_enters());
        EXPECT_EQ(1, delegate->num_updates());
        EXPECT_EQ(0, delegate->num_exits());
        EXPECT_EQ(0, delegate->num_drops());
        EXPECT_EQ(point1_in_target_widget.ToString(),
            delegate->last_event_mouse_position().ToString());
        EXPECT_EQ(modifier_flags, delegate->last_event_flags());

        client->OnMouseMovement(point2_in_screen, modifier_flags,
            ui::EventTimeForNow());
        EXPECT_EQ(1, delegate->num_enters());
        EXPECT_EQ(2, delegate->num_updates());
        EXPECT_EQ(0, delegate->num_exits());
        EXPECT_EQ(0, delegate->num_drops());
        EXPECT_EQ(point2_in_target_widget.ToString(),
            delegate->last_event_mouse_position().ToString());
        EXPECT_EQ(modifier_flags, delegate->last_event_flags());

        client->OnMouseReleased();
        EXPECT_EQ(1, delegate->num_enters());
        EXPECT_EQ(2, delegate->num_updates());
        EXPECT_EQ(0, delegate->num_exits());
        EXPECT_EQ(1, delegate->num_drops());
        EXPECT_EQ(point2_in_target_widget.ToString(),
            delegate->last_event_mouse_position().ToString());
        EXPECT_EQ(modifier_flags, delegate->last_event_flags());

        EXPECT_FALSE(client->IsMoveLoopRunning());
    }

} // namespace

TEST_F(DesktopDragDropClientAuraX11ChromeSourceTargetTest, Basic)
{
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&ChromeSourceTargetStep2, client(), ui::EF_NONE));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}

// Test that if 'Ctrl' is pressed during a drag and drop operation, that
// the aura::client::DragDropDelegate is properly notified.
TEST_F(DesktopDragDropClientAuraX11ChromeSourceTargetTest, CtrlPressed)
{
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&ChromeSourceTargetStep2, client(), ui::EF_CONTROL_DOWN));
    int result = StartDragAndDrop();
    EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}

} // namespace views
